//
//  TLTestLabel.m
//  TLLabel
//
//  Created by Tianlong on 2017/6/16.
//  Copyright © 2017年 Tianlong. All rights reserved.
//

#import "TLLabel.h"

@interface TLLabel ()<NSLayoutManagerDelegate>

/** 在layoutsubview，setFrame，setBounds中更新了 */
@property (nonatomic , strong) NSTextContainer *textContainer;
/** 这个属性只是为了配合 textContainer 使用*/
@property (nonatomic , strong) NSLayoutManager *layoutManager;
/** 在设置属性文字的时候赋值 */
@property (nonatomic, retain) NSTextStorage *textStorage;

/** 存储高亮文字和高亮文字range的字典数组 */
@property (nonatomic , strong) NSArray *linkRanges;
/** 忽略的字符串的集合 */
@property (nullable, nonatomic, strong) NSSet *ignoredKeywords;
/** 点击的那段文字 */
@property (nonatomic , assign) NSRange selectedRange;
/** 保存原有的文字设置：例如颜色，字号，等 */
@property (nonatomic , strong) NSMutableDictionary *originalDict;
@end

@implementation TLLabel

static NSString * const LinkRangeKey = @"range";
static NSString * const LinkContentKey = @"link";

-(instancetype)initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame:frame]) {
        
        //第1步 设置基本数据
        [self setupTextSystem];
        
    }
    return self;
}

-(instancetype)initWithCoder:(NSCoder *)aDecoder{
    if(self = [super initWithCoder:aDecoder]){
        
    }
    return self;
}

#pragma mark - lazyLoad
-(NSArray *)linkRanges{
    if (_linkRanges == nil) {
        _linkRanges = [NSArray array];
    }
    return _linkRanges;
}

#pragma mark - 第1步：设置基本属性
-(void)setupTextSystem{
    
    self.userInteractionEnabled = YES;
    
    _textContainer = [[NSTextContainer alloc] init];
    _textContainer.lineFragmentPadding = 0;
    _textContainer.maximumNumberOfLines = self.numberOfLines;
    _textContainer.lineBreakMode = self.lineBreakMode;
    _textContainer.size = self.frame.size;
    

    _layoutManager = [[NSLayoutManager alloc] init];
    _layoutManager.delegate = self;
    [_layoutManager addTextContainer:_textContainer];
    
    [_textContainer setLayoutManager:_layoutManager];
    
    UITapGestureRecognizer * tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
    [self addGestureRecognizer:tapGesture];
}

#pragma mark - set 方法
-(void)setAttributeDict:(NSDictionary *)attributeDict{
    _attributeDict = attributeDict;
    [self updateAttributeText];
}

-(void)setAttributeTextArray:(NSArray *)attributeTextArray{
    _attributeTextArray = attributeTextArray;
    [self updateAttributeText];
}

#pragma mark - 赋值textStorage，设置属性文字
- (void)updateAttributeText{
    //设置高亮文字
    [self highLightText];
    //更新 textStorage
    [self updateTextStorage];
}

#pragma mark - 更新 textStorage
-(void)updateTextStorage{
    //设置textStorage
    if (_textStorage){
        [_textStorage setAttributedString:self.attributedText];
    }
    else{
        _textStorage = [[NSTextStorage alloc] initWithAttributedString:self.attributedText];
        [_textStorage addLayoutManager:_layoutManager];
        [_layoutManager setTextStorage:_textStorage];
    }
}

#pragma mark - 点击了某段文字
- (void)tap:(UITapGestureRecognizer *)tapGesture{
    
    NSDictionary *touchedLink;
    CGPoint touchLocation = [tapGesture locationInView:self];
    touchedLink = [self linkAtPoint:touchLocation];
    
    if (touchedLink){
        self.selectedRange = [[touchedLink objectForKey:LinkRangeKey] rangeValue];
        //点击的文字在哪个range，文字一并传递出去
        NSRange touchedRange = [[touchedLink objectForKey:LinkRangeKey] rangeValue];
        NSString *touchedSubstring = [touchedLink objectForKey:LinkContentKey];
        
        if (_touchedHighLightText) {
            _touchedHighLightText(self,touchedSubstring,touchedRange);
        }
        
        if ([self.delegate respondsToSelector:@selector(tlLabel:touchedText:touchedRange:)]) {
            [self.delegate tlLabel:self touchedText:touchedSubstring touchedRange:touchedRange];
        }
        NSLog(@"touchedSubstring = %@ touchedRange = %@",touchedSubstring,NSStringFromRange(touchedRange));
    }
    else{
        //没有点在高亮的文字上
        if(_touchedOtherText){
            _touchedOtherText();
        }
        
        if ([self.delegate respondsToSelector:@selector(touchedOtherText:)]) {
            [self.delegate touchedOtherText:self];
        }
        NSLog(@"没有点在高亮的文字上");
    }
    self.selectedRange = NSMakeRange(0, 0);
}

#pragma mark - tap: -> linkAtPoint（检查点击的文字在哪个位置哪个，然后返回该位置下对应的字典）
- (NSDictionary *)linkAtPoint:(CGPoint)location{
    
    if (_textStorage.string.length == 0){
        return nil;
    }
    
    CGPoint textOffset = [self calcGlyphsPositionInView];
    
    location.x -= textOffset.x;
    location.y -= textOffset.y;
    
    NSUInteger touchedChar = [_layoutManager glyphIndexForPoint:location inTextContainer:_textContainer];
    
    NSRange lineRange;
    CGRect lineRect = [_layoutManager lineFragmentUsedRectForGlyphAtIndex:touchedChar effectiveRange:&lineRange];
    if (CGRectContainsPoint(lineRect, location) == NO)
        return nil;
    
    for (NSDictionary *dictionary in self.linkRanges){
        NSRange range = [[dictionary objectForKey:LinkRangeKey] rangeValue];
        if ((touchedChar >= range.location) && touchedChar < (range.location + range.length)){
            return dictionary;
        }
    }
    return nil;
}


#pragma mark - 能够计算出点击的文字的位置，这几个方法很重要：textRectForBounds + drawTextInRect
/** 这三个方法来更新_textContainer的尺寸 */
- (void)setFrame:(CGRect)frame{
    [super setFrame:frame];
    _textContainer.size = self.bounds.size;
}

- (void)setBounds:(CGRect)bounds{
    [super setBounds:bounds];
    _textContainer.size = self.bounds.size;
}

- (void)layoutSubviews{
    [super layoutSubviews];
    _textContainer.size = self.bounds.size;
}

- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines{
    
    CGSize savedTextContainerSize = _textContainer.size;
    NSInteger savedTextContainerNumberOfLines = _textContainer.maximumNumberOfLines;
    
    _textContainer.size = bounds.size;
    _textContainer.maximumNumberOfLines = numberOfLines;
    
    CGRect textBounds = [_layoutManager usedRectForTextContainer:_textContainer];
    
    textBounds.origin = bounds.origin;
    textBounds.size.width = ceil(textBounds.size.width);
    textBounds.size.height = ceil(textBounds.size.height);
    
    if (textBounds.size.height < bounds.size.height){
        CGFloat offsetY = (bounds.size.height - textBounds.size.height) / 2.0;
        textBounds.origin.y += offsetY;
    }

    _textContainer.size = savedTextContainerSize;
    _textContainer.maximumNumberOfLines = savedTextContainerNumberOfLines;
    
    return textBounds;
}

- (void)drawTextInRect:(CGRect)rect{
    
    if (self.attributeDict != nil && self.attributeTextArray.count > 0) {
        NSRange glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer];
        CGPoint glyphsPosition = [self calcGlyphsPositionInView];
        
        [_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:glyphsPosition];
        [_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:glyphsPosition];
    }
    else{
        [super drawTextInRect:rect];
    }
}

#pragma mark - 计算点击的文字所处的位置
- (CGPoint)calcGlyphsPositionInView{
    
    CGPoint textOffset = CGPointZero;
    CGRect textBounds = [_layoutManager usedRectForTextContainer:_textContainer];
    textBounds.size.width = ceil(textBounds.size.width);
    textBounds.size.height = ceil(textBounds.size.height);
    
    if (textBounds.size.height < self.bounds.size.height){
        CGFloat paddingHeight = (self.bounds.size.height - textBounds.size.height) / 2.0;
        textOffset.y = paddingHeight;
    }
    
    return textOffset;
}

#pragma mark - highLightText(文字高亮处理，并将高亮的文字和对应的range都保存起来)
-(void)highLightText{
    //生成要匹配的正则字符串
    NSString *regularString = [self regularPattern:self.attributeTextArray];
    //生成NSRegularExpression 正则匹配对象
    NSRegularExpression *regExpression = [NSRegularExpression regularExpressionWithPattern:regularString options:0 error:nil];
    //通过正则匹配设置高亮
    [self setAttributeWithRegularExpression:regExpression attributes:self.attributeDict];
}

/**拼接要被高亮的字符串，并拼接成新串返回
 正则匹配要被高亮的文字，并以字符串的格式返回
 @param keys 高亮文字的数组
 */
-(NSString *)regularPattern:(NSArray *)keys{
    
    NSMutableString *pattern = [[NSMutableString alloc]initWithString:@"(?i)"];
    for (NSString *key in keys) {
        [pattern appendFormat:@"%@|",key];
    }
    return pattern;
}

/**设置文本高亮
 @param expression 正则匹配对象
 @param attributesDict 文字属性配置
 */
- (void)setAttributeWithRegularExpression:(NSRegularExpression *)expression
                               attributes:(NSDictionary *)attributesDict{
    
    //获取非高亮状态下文字的一些属性：如果颜色，字号等
    if (self.originalDict == nil) {
        //默认一个原来的字体和颜色
        self.originalDict = [NSMutableDictionary dictionary];
        [self.originalDict setObject:[UIColor blackColor] forKey:NSForegroundColorAttributeName];
        [self.originalDict setObject:[UIFont systemFontOfSize:17] forKey:NSFontAttributeName];
    }
    
    NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:self.text attributes:self.originalDict];
    
    __weak typeof(self)weakSelf = self;
    NSMutableArray *linkM = [NSMutableArray array];
    [expression enumerateMatchesInString:self.text
                                 options:0
                                   range:NSMakeRange(0, [self.text length])
                              usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
                                  
                                  NSRange matchRange = [result range];
                                  NSString *matchString = [weakSelf.text substringWithRange:matchRange];
                                  
                                  //保存高亮的range 和 字符串
                                  if (![weakSelf ignoreMatch:matchString] && [weakSelf checkStringInAttributeTextArray:matchString]){
                                      [linkM addObject:@{
                                                         LinkRangeKey : [NSValue valueWithRange:matchRange],
                                                         LinkContentKey : matchString
                                                        }];
                                      NSLog(@"\nmatchRange = %@ \nmatchString = %@ \n\n",NSStringFromRange(matchRange),matchString);
                                  }
                                  
                                  weakSelf.linkRanges = [NSArray arrayWithArray:linkM];
                                  
                                  if (attributesDict) {
                                      [attributedString addAttributes:attributesDict range:matchRange];
                                  }
                                  
                              }];
    [self setAttributedText:attributedString];
}

#pragma mark - 在传入的要高亮的字符串数组中，查看与正则匹配后的字符串，并保存起来
-(BOOL)checkStringInAttributeTextArray:(NSString *)keyStr{
    BOOL find = NO;
    for (NSString *str in self.attributeTextArray) {
        if ([keyStr isEqualToString:str]) {
            find = YES;
            break;
        }
    }
    return find;
}

#pragma mark - 检验是否是要忽略的内容
- (BOOL)ignoreMatch:(NSString*)string{
    return [_ignoredKeywords containsObject:[string lowercaseString]];
}

- (void)setIgnoredKeywords:(NSSet *)ignoredKeywords{
    NSMutableSet *set = [NSMutableSet setWithCapacity:ignoredKeywords.count];
    [ignoredKeywords enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
        [set addObject:[obj lowercaseString]];
    }];
    _ignoredKeywords = [set copy];
}

#pragma mark - Layout manager delegate
-(BOOL)layoutManager:(NSLayoutManager *)layoutManager shouldBreakLineByWordBeforeCharacterAtIndex:(NSUInteger)charIndex{
    NSRange range;
    NSURL *linkURL = [layoutManager.textStorage attribute:NSLinkAttributeName atIndex:charIndex effectiveRange:&range];
    return !(linkURL && (charIndex > range.location) && (charIndex <= NSMaxRange(range)));
}


#pragma mark - 重写原属性的一些设置：字号，行数，颜色，属性文字等
- (void)setNumberOfLines:(NSInteger)numberOfLines{
    [super setNumberOfLines:numberOfLines];
    _textContainer.maximumNumberOfLines = numberOfLines;
}

-(void)setText:(NSString *)text{
    [super setText:text];
    [self updateAttributeText];
}

-(void)setTextColor:(UIColor *)textColor{
    [super setTextColor:textColor];
    //保存原有的属性
    if (self.originalDict == nil) {
        self.originalDict = [NSMutableDictionary dictionary];
    }
    [self.originalDict setObject:self.textColor forKey:NSForegroundColorAttributeName];
}

-(void)setFont:(UIFont *)font{
    [super setFont:font];
    //保存原有的属性
    if (self.originalDict == nil) {
        self.originalDict = [NSMutableDictionary dictionary];
    }
    [self.originalDict setObject:self.font forKey:NSFontAttributeName];
}


@end
